package com.bosssoft.hr.train.springmvc.example.aspect;

import com.bosssoft.hr.train.springmvc.example.annotation.MyLogApi;
import com.bosssoft.hr.train.springmvc.example.exception.BusinessException;
import com.bosssoft.hr.train.springmvc.example.model.User;
import com.bosssoft.hr.train.springmvc.example.util.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * @description 日志以及异常处理切面（controller）
 * controller进行日志管理，同时对于controller的方法的执行进行异常处理抛出GlobalException
 * @author ChenTong
 * @date 2020/6/6
 */
@Slf4j
@Component
@Aspect
public class LogAndErrAspect {
    /**
     * 设置切入点
     * 使用MyLogApi的所有方法 method返回值 method位置 method参数
     */
    @Pointcut("@annotation(com.bosssoft.hr.train.springmvc.example.annotation.MyLogApi)")
    public void pointcut(){
        log.info("I'm an aspect - log ");
    }

    /**
     * 通知环绕
     * 方法执行前 方法执行 方法执行后
     * @param joinPoint ProceedingJoinPoint
     * @throws Throwable
     */
    @Around("pointcut()")
    public void log(ProceedingJoinPoint joinPoint) {
        long startTime = System.currentTimeMillis();
        //获取请求信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String reqMsg = this.getRequestMsg(request);
        //请求参数
        Object[] args = joinPoint.getArgs();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取joinPoint所有的methods->为了获取具有注解的方法的注解内容
        Method[] methods = joinPoint.getTarget().getClass().getMethods();
        //注解中内容
        String logMsg = this.getAnnotationMsg(methods,methodName);
        //method调用并产生结果
        Object result = null;
        //执行 sonarlint 检测有错误尝试多种方法 不知道如何解决
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error(throwable.getMessage(),throwable);
            throw new BusinessException(ResultCode.AOP_PROCESS_ERR,throwable);
        }
        //计算运行时间
        long execTime = System.currentTimeMillis()-startTime;
        //保存日志
        log.info("regMsg:{}, logMsg:{}, args:{}, execTime:{}ms, resp:{}",reqMsg,logMsg, Arrays.toString(args),execTime,result.toString());
    }

    /**
     * 若切点产生异常,触犯该方法,获取原来异常信息并抛出自定义全局异常
     * 全局异常将被ExceptionHandler处理：向客户端响应Result类对象的json数据
     * @param e 异常cause
     */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void err(Exception e){
        log.error(e.getMessage(),e);
        //返回异常，exceptionHandler将会处理该异常并响应Result类对象的json数据
        throw new BusinessException(ResultCode.RESPONSE_FAIL,e);
    }

    /**
     * 获取请求方法的详细信息
     * @param res HttpServletRequest 请求
     * @return String Request信息
     */
    private String getRequestMsg(HttpServletRequest res){
        //获取请求
        if(res==null) return "requestMsg=[]";
        res = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取session
        HttpSession session = res.getSession();
        //获取请求ip
        String ip = res.getRemoteAddr();
        //请求者

        String reqUser = "Customer";
        if (session.getAttribute("USER_SESSION")!=null){
            ((User) session.getAttribute("USER_SESSION")).getName();
        }
        //请求时间
        String reqTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

        return String.format("[IP-%s,reqUser-%s,reqTim-%s]", ip,reqUser,reqTime);

    }

    /**
     * 获取注解中日志信息
     * @param methods joinPoint对应方法列表
     * @param targetMethod 目标函数方法名
     * @return
     */
    private String getAnnotationMsg(Method[] methods,String targetMethod){
        if (methods==null) return "logMsg:";
        String logMsg = "";
        for (Method method: methods){
            if (method.getName().equals(targetMethod)){
                MyLogApi logApi = method.getAnnotation(MyLogApi.class);
                logMsg = logApi.msg();
                break;
            }
        }
        return logMsg;
    }
}
